/*
 * TimeDate.c
 *
 *  Created on: 20.09.2011
 *      Author: windows97
 */

#include <avr/io.h>
#include <stdlib.h>
#include <avr/eeprom.h>
#include "Globals.h"
#include "HwAbstraction.h"
#include "TimeDate.h"

uint8_t NvmStorageTime_u8[8] EEMEM;

typedef struct _T_TimeDateNotifications
{
  T_TimeChangeNotify        Function_pt;
  T_TimeChangeNotifyReason  Reason_t;
}T_TidNotifications;

typedef struct _T_TimeDateNotify
{
  uint8_t Second_t  :1;
  uint8_t DayChange_t :1;
  uint8_t Once_t    :1;
}T_TimeDateNotify;

#define TIMEDATE_NO_OF_NOTIFICATIONS 8

static T_TimeDate         gTimeDate_t;
static volatile T_TimeDateNotify gTidNotify_t;
static T_TidNotifications gTimeDateNotifications_t[TIMEDATE_NO_OF_NOTIFICATIONS];

static uint8_t TidGetDaysofMonth( T_Month iMonth_t, uint8_t iYear_u8 );
static void TidSendNotifications( T_TimeChangeNotifyReason iReason_t );
static void TidUpdateTimeDate( void );
void TidDayChangeNotification( void );
void TidTimeSetNotification( void );

void TidSetTimeDate( T_TimeDate* iTimeDate )
{
  if( NULL != iTimeDate )
  {
    gTimeDate_t = *iTimeDate;
    /* TODO (windows97 11-11-02): Check real time changes not only the minute changes */
    TidSendNotifications( TimeSetChange );
  }
}

void TidGetTimeDate( T_TimeDate* iTimeDate )
{
  *iTimeDate = gTimeDate_t;
}

void TidGetTime( T_Time* iTime )
{
  *iTime = gTimeDate_t.Time;
}

void TidGetDate( T_Date* iDate )
{
  *iDate = gTimeDate_t.Date;
}

void TidUpdateSecond( void )
{
  /* This value should be never more than one! */
  gTidNotify_t.Second_t = 1;
}

void TidRegisterNotification( T_TimeChangeNotify iNotifyFunction, T_TimeChangeNotifyReason iReason )
{
  uint8_t lCount = 0;
  uint8_t lFreeSlot = TIMEDATE_NO_OF_NOTIFICATIONS;
  for( lCount = 0; lCount < TIMEDATE_NO_OF_NOTIFICATIONS; lCount++ )
  {
    if( ( gTimeDateNotifications_t[lCount].Function_pt == NULL   ) &&
        ( gTimeDateNotifications_t[lCount].Reason_t   == NotSet ) )
    {
      lFreeSlot = lCount;
    }
  }
  if( lFreeSlot < TIMEDATE_NO_OF_NOTIFICATIONS )
  {
    if( iNotifyFunction != NULL )
    {
      gTimeDateNotifications_t[lFreeSlot].Function_pt = iNotifyFunction;
      gTimeDateNotifications_t[lFreeSlot].Reason_t   = iReason;
    }
  }
}

void TidInit(void)
{
  uint8_t lCount = 0;
  for( lCount = 0; lCount < TIMEDATE_NO_OF_NOTIFICATIONS; lCount++ )
  {
    gTimeDateNotifications_t[lCount].Function_pt = NULL;
    gTimeDateNotifications_t[lCount].Reason_t   = NotSet;
  }
  TidRegisterNotification( TidDayChangeNotification, DayChange );
  TidRegisterNotification( TidTimeSetNotification, TimeSetChange );
}

void TidCyclic( void )
{
  /* check if second has been changed */
  if( 0 != gTidNotify_t.Second_t )
  {
    gTidNotify_t.Second_t = 0;
    TidUpdateTimeDate( );
  }

  /* check for day change */
  if( 0 != gTidNotify_t.DayChange_t )
  {
    gTidNotify_t.DayChange_t = 0;
    /* write current time to eeprom */
    eeprom_write_block( &gTimeDate_t, NvmStorageTime_u8, sizeof(T_TimeDate) );
    uart_puts( "Time written!\r\n");
  }
}

static uint8_t TidGetDaysofMonth( T_Month iMonth_t, uint8_t iYear_u8 )
{
  uint8_t lDays_u8 = 31;
  switch(iMonth_t)
  {
    case January:
    case March:
    case May:
    case July:
    case August:
    case October:
    case December:
    {
      lDays_u8 = 31;
      break;
    }
    case April:
    case June:
    case September:
    case November:
    {
      lDays_u8 = 30;
      break;
    }
    case February:
    {
      if( ( iYear_u8 % 4 == 0 ) ||
          ( iYear_u8 % 100 == 0 ))
      {
        lDays_u8 = 29;
      }
      else
      {
        lDays_u8 = 28;
      }
      break;
    }
    default:
    {
      lDays_u8 = 31;
      break;
    }
  }
  return (lDays_u8);
}

static void TidSendNotifications( T_TimeChangeNotifyReason iReason_t )
{
  for( uint8_t lCount = 0; lCount < TIMEDATE_NO_OF_NOTIFICATIONS; lCount++ )
  {
    if( ( gTimeDateNotifications_t[lCount].Function_pt != NULL      ) &&
        ( gTimeDateNotifications_t[lCount].Reason_t    == iReason_t ) )
    {
      gTimeDateNotifications_t[lCount].Function_pt();
    }
  }
}

void TidDayChangeNotification( void )
{
  gTidNotify_t.DayChange_t = 1;
}

void TidTimeSetNotification( void )
{
  gTidNotify_t.Once_t = 1;
}

/**
 *  Purpose of the function described in a single sentence.
 *  Here a more detailed description follows.
 *
 *  @param            param1_u16   Description of parameter 1
 *  @param            param2_u16   Description of parameter 2
 *
 *  @pre              Description of a pre-condition if any
 *  @post             Description of a post-condition if any
 *
 *  @return           Description of the return parameter of the function
 *                    (description of special return values, e.g. error codes)
 *
 */

static void TidUpdateTimeDate( void )
{
  gTimeDate_t.Time.Second++;
  TidSendNotifications( SecondChange );
  if (gTimeDate_t.Time.Second >= TIME_SEC_MAX)
  {
    TidSendNotifications( MinuteChange );
    gTimeDate_t.Time.Second = 0;
    gTimeDate_t.Time.Minute++;
    if(gTimeDate_t.Time.Minute >= TIME_MIN_MAX)
    {
      TidSendNotifications( HourChange );
      gTimeDate_t.Time.Minute = 0;
      gTimeDate_t.Time.Hour++;
      if(gTimeDate_t.Time.Hour >= TIME_HOUR_MAX)
      {
        TidSendNotifications( DayChange );
        gTimeDate_t.Time.Hour = 0;
        gTimeDate_t.Date.Day++;
        if(gTimeDate_t.Date.Day >= TidGetDaysofMonth( gTimeDate_t.Date.Month, gTimeDate_t.Date.Year ))
        {
          TidSendNotifications( MonthChange );
          gTimeDate_t.Date.Day = 1;
          gTimeDate_t.Date.Month++;
          if( gTimeDate_t.Date.Month >= DATE_MON_MAX )
          {
            TidSendNotifications( YearChange );
            gTimeDate_t.Date.Month = 1;
            gTimeDate_t.Date.Year++;
          }
        }
      }
    }
  }
}

void TidGetStoredTimeDate( void )
{
  eeprom_read_block( &gTimeDate_t, NvmStorageTime_u8, sizeof(T_TimeDate) );
  if( gTimeDate_t.Time.Second >= TIME_SEC_MAX )
  {
    UART_PUT_VALUE( "Sec: ", gTimeDate_t.Time.Second );
    gTimeDate_t.Time.Second = 0;
  }
  if( gTimeDate_t.Time.Minute >= TIME_MIN_MAX )
  {
    UART_PUT_VALUE( "Min: ", gTimeDate_t.Time.Minute );
    gTimeDate_t.Time.Minute = 0;
  }
  if( gTimeDate_t.Time.Hour >= TIME_HOUR_MAX )
  {
    UART_PUT_VALUE( "Hour: ", gTimeDate_t.Time.Hour );
    gTimeDate_t.Time.Hour = 0;
  }
  if( gTimeDate_t.Date.Day >= DATE_DAY_MAX )
  {
    UART_PUT_VALUE( "Day: ", gTimeDate_t.Date.Day );
    gTimeDate_t.Date.Day = 1;
  }
  if( gTimeDate_t.Date.WeekDay >= DATE_WDAY_MAX )
  {
    UART_PUT_VALUE( "WDay: ", gTimeDate_t.Date.WeekDay );
    gTimeDate_t.Date.WeekDay = Monday;
  }
  if( gTimeDate_t.Date.Month >= DATE_MON_MAX )
  {
    UART_PUT_VALUE( "Month: ", gTimeDate_t.Date.Month );
    gTimeDate_t.Date.Month = DATE_MON_MIN;
  }
  if( gTimeDate_t.Date.Year >= DATE_YEAR_MAX )
  {
    UART_PUT_VALUE( "Year: ", gTimeDate_t.Date.Year );
    gTimeDate_t.Date.Year = 0;
  }
}
